//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reflection;
using EffiProz.Core.Lib;
using EffiProz.Core.Resources;



namespace EffiProz.Core
{
    /**@todo fredt - move Trace.doAssert() literals to Trace*/

    /**
     * Provides information about HSQLDB SQL-invoked routines and SQL functions. <p>
     *
     * In particular, this class provides information about Java Methods _in a form
     * compatible with presentation via the related HSQLDB system tables,
     * SYSTEM_PROCEDURES and SYSTEM_PROCEDURECOLUMNS, involved _in the production of
     * the DatabaseMetaData getProcedures and getProcedureColumns result sets.
     *
     * @author  boucherb@users
     * @version 1.7.2
     * @since 1.7.2
     */
    sealed public class DIProcedureInfo
    {

        // java.sql dependencies mostly removed
        public const string conClsName = "EffiProz.Core.ServerConnection";
        public const int procedureResultUnknown = 0;
        public const int procedureNoResult = 1;
        public const int procedureReturnsResult = 2;
        public const int procedureColumnUnknown = 0;
        public const int procedureColumnIn = 1;
        public const int procedureColumnInOut = 2;
        public const int procedureColumnResult = 3;
        public const int procedureColumnOut = 4;
        public const int procedureColumnReturn = 5;
        public const int procedureNoNulls = 0;
        public const int procedureNullable = 1;
        public const int procedureNullableUnknown = 2;
        private Type clazz;
        private Type[] colClasses;
        private int[] colTypes;
        private int _colOffset;
        private int colCount;
        private bool colsResolved;
        private string fqn;
        private string specificName;
        private int hnd_remarks;
        private MethodInfo method;
        private string sig;
        private DINameSpace nameSpace;
        private Dictionary<Type, int> typeMap = new Dictionary<Type, int>();

        public DIProcedureInfo(DINameSpace ns)
        {
            setNameSpace(ns);
        }

        private int colOffset()
        {

            if (!colsResolved)
            {
                resolveCols();
            }

            return _colOffset;
        }

        public List<string> getAliases()
        {
            return (List<string>)nameSpace.getInverseAliasMap()[getFQN()];
        }

        public Type getColClass(int i)
        {
            return colClasses[i + colOffset()];
        }

        public int getColCount()
        {

            if (!colsResolved)
            {
                resolveCols();
            }

            return colCount;
        }

        public int getColDataType(int i)
        {
            return (getColTypeCode(i));
        }

        public int? getColLen(int i)
        {

            int size;
            int type;

            type = getColTypeCode(i);

            switch (type)
            {

                case Types.BINARY:
                case Types.LONGVARBINARY:
                case Types.VARBINARY:
                    {
                        size = int.MaxValue;

                        break;
                    }
                case Types.BIGINT:
                case Types.DOUBLE:
                case Types.DATE:
                case Types.FLOAT:
                case Types.TIME:
                    {
                        size = 8;

                        break;
                    }
                case Types.TIMESTAMP:
                    {
                        size = 12;

                        break;
                    }
                case Types.REAL:
                case Types.INTEGER:
                    {
                        size = 4;

                        break;
                    }
                case Types.SMALLINT:
                    {
                        size = 2;

                        break;
                    }
                case Types.TINYINT:
                case Types.BOOLEAN:
                    {
                        size = 1;

                        break;
                    }
                default:
                    size = 0;
                    break;
            }

            return (size == 0) ? null
                               : (int?)size;
        }

        public string getColName(int i)
        {
            return CompiledStatement.PCOL_PREFIX + (i + colOffset());
        }

        public int getColNullability(int i)
        {

            int cn;

            cn = getColClass(i).IsPrimitive ? procedureNoNulls
                                              : procedureNullable;

            return (cn);
        }

        public string getColRemark(int i)
        {

            string key;
            StringBuilder sb;

            sb = new StringBuilder(getSignature());
            key = sb.Append('@').Append(i + colOffset()).ToString();

            return BundleHandler.getString(hnd_remarks, key);
        }

        // JDBC sort-contract:
        // _out return value column, then in/in out/out parameter columns
        // _in formal order, then result columns _in column order
        //
        // Currently, we materialize the java method return value, if
        // any, as a result column, not as an OUT return value column, so
        // it should actually appear _after_ the other procedure columns
        // _in the row order returned by the JDBC getProcedureColumns() method
        public int getColSequence(int i)
        {

            // colOffset has the side-effect of setting colCount properly
            return (i + colOffset() == 0) ? colCount
                                          : i;
        }

        public int getColTypeCode(int i)
        {

            i += colOffset();

            return colTypes[i];
        }

        public int getColUsage(int i)
        {

            switch (i + colOffset())
            {

                case 0:
                    {

                        // Currently, we materialize the java method return value, if
                        // any, as a result column, not as an OUT return column
                        return (procedureColumnResult);
                    }

                // todo: registration and reporting on result columns for routines
                //       that generate real" result sets
                default:
                    {

                        // We could get religious here and maybe report IN OUT
                        // for newRow of before update for each row trigger methods,
                        // but there's not really any added value
                        return (procedureColumnIn);
                    }
            }
        }

        public Type getDeclaringClass()
        {
            return this.clazz;
        }

        public string getFQN()
        {

            StringBuilder sb;

            if (fqn == null)
            {
                sb = new StringBuilder();
                fqn = sb.Append(clazz.FullName).Append('.').Append(
                    method.Name).ToString();
            }

            return fqn;
        }

        public string getSpecificName()
        {

            if (specificName == null)
            {
                specificName = clazz.FullName + "." + getSignature();
            }

            return specificName;
        }

        public int getInputParmCount()
        {
            return (method.GetParameters().Length);
        }

        public MethodInfo getMethod()
        {
            return this.method;
        }

        public string getOrigin(string srcType)
        {
            return (nameSpace.isBuiltin(clazz) ? "BUILTIN "
                                               : "USER DEFINED ") + srcType;
        }

        public int getOutputParmCount()
        {

            // no support for IN OUT or OUT columns yet
            return (0);
        }

        public string getRemark()
        {
            return BundleHandler.getString(hnd_remarks, getSignature());
        }

        public int getResultSetCount()
        {
            return (method.ReturnType == typeof(void)) ? (0)
                                                         : (1);
        }

        public int getResultType(string origin)
        {

            int type;

            type = !"ROUTINE".Equals(origin) ? procedureResultUnknown
                                             : method.ReturnType
                                               == typeof(void) ? procedureNoResult
                                                            : procedureReturnsResult;

            return (type);
        }

        public string getSignature()
        {

            if (sig == null)
            {
                sig = DINameSpace.getSignature(method);
            }

            return sig;
        }

        /**
         * Retrieves the specific name of the given Method object. <p>
         *
         * @param m The Method object for which to retreive the specific name
         * @return the specific name of the specified Method object.
         */
        public static string getMethodSpecificName(MethodInfo m)
        {

            return m == null ? null
                             : m.DeclaringType.FullName + '.'
                               + DINameSpace.getSignature(m);
        }

        public DINameSpace getNameSpace()
        {
            return nameSpace;
        }

        public void setNameSpace(DINameSpace ns)
        {

            nameSpace = ns;

            Type c;
            int type;

            // can only speed up test significantly for java.lang.Object,
            // const classes, primitive types and hierachy parents.
            // Must still check later if assignable from candidate classes, where
            // hierarchy parent is not final.
            //ARRAY
            try
            {
                c = nameSpace.classForName("System.Array");

                typeMap.Add(c, (Types.ARRAY));
            }
            catch (Exception) { }

            // BIGINT
            type = (Types.BIGINT);

            typeMap.Add(typeof(long), type);

            // bool
            type = (Types.BOOLEAN);

            typeMap.Add(typeof(bool), type);


            // BLOB
            type = (Types.BLOB);

            try
            {
                c = nameSpace.classForName("org.hsqldb.jdbc.jdbcBlob");

                typeMap.Add(c, type);
            }
            catch (Exception) { }

            // CHAR
            type = (Types.CHAR);

            typeMap.Add(typeof(char), type);

            typeMap.Add(typeof(char[]), type);

            // CLOB
            type = (Types.CLOB);

            try
            {
                c = nameSpace.classForName("org.hsqldb.jdbc.jdbcClob");

                typeMap.Add(c, type);
            }
            catch (Exception) { }

            // DATALINK
            type = (Types.DATALINK);

            typeMap.Add(typeof(System.Uri), type);

            // DATE
            type = (Types.DATE);

            typeMap.Add(typeof(DateTime), type);

            // DECIMAL
            type = (Types.DECIMAL);

            try
            {

                typeMap.Add(typeof(Decimal), type);
            }
            catch (Exception) { }

            // DISTINCT
            try
            {
                c = nameSpace.classForName("org.hsqldb.jdbc.jdbcDistinct");

                typeMap.Add(c, (Types.DISTINCT));
            }
            catch (Exception) { }

            // DOUBLE
            type = (Types.DOUBLE);

            typeMap.Add(typeof(double), type);


            // FLOAT : Not actually a legal IN parameter type yet
            type = (Types.FLOAT);

            typeMap.Add(typeof(Single), type);


            // INTEGER
            type = (Types.INTEGER);

            typeMap.Add(typeof(int), type);

            // JAVA_OBJECT
            type = (Types.JAVA_OBJECT);

            typeMap.Add(typeof(object), type);

            // LONGVARBINARY
            type = (Types.LONGVARBINARY);

            typeMap.Add(typeof(byte[]), type);


            // LONGVARCHAR
            type = (Types.LONGVARCHAR);

            typeMap.Add(typeof(string), type);

            // NULL
            type = (Types.NULL);

            typeMap.Add(typeof(void), type);

            // REF
            type = (Types.REF);

            try
            {
                c = nameSpace.classForName("org.hsqldb.jdbc.jdbcRef");

                typeMap.Add(c, type);
            }
            catch (Exception ) { }

            // SMALLINT : Not actually a legal IN parameter type yet
            type = (Types.SMALLINT);

            typeMap.Add(typeof(short), type);

            // STRUCT :
            type = (Types.STRUCT);

            try
            {
                c = nameSpace.classForName("org.hsqldb.jdbc.jdbcStruct");

                typeMap.Add(c, type);
            }
            catch (Exception ) { }

            // TIME
            //type = (Types.TIME);

            //typeMap.Add(typeof(DateTime), type);

            //// TIMESTAMP
            //type = (Types.TIMESTAMP);

            //typeMap.Add(typeof(DateTime), type);

            // TINYINT : Not actually a legal IN parameter type yet
            type = (Types.TINYINT);

            typeMap.Add(typeof(byte), type);

            // XML : Not actually a legal IN parameter type yet
            type = (Types.XML);

            try
            {
                c = nameSpace.classForName("org.w3c.dom.Document");

                typeMap.Add(c, type);

                c = nameSpace.classForName("org.w3c.dom.DocumentFragment");

                typeMap.Add(c, type);
            }
            catch (Exception) { }
        }

        private void resolveCols()
        {

            Type rType;
            ParameterInfo[] pTypes;
            Type clazz;
            int ptlen;
            int pclen;
            bool isFPCON;

            rType = method.ReturnType;
            pTypes = method.GetParameters();
            ptlen = pTypes.Length;
            isFPCON = ptlen > 0 && pTypes[0].GetType().Name.Equals(conClsName);
            pclen = 1 + ptlen - (isFPCON ? 1
                                                 : 0);
            colClasses = new Type[pclen];
            colTypes = new int[pclen];
            colClasses[0] = rType;
            colTypes[0] = typeForClass(rType);

            for (int i = isFPCON ? 1
                                 : 0, idx = 1; i < ptlen; i++, idx++)
            {
                clazz = pTypes[i].GetType();
                colClasses[idx] = clazz;
                colTypes[idx] = typeForClass(clazz);
            }

            _colOffset = rType == typeof(void) ? 1
                                           : 0;
            colCount = pclen - _colOffset;
        }

        /**
         * This requires the following properties files:
         *
         * org_hsqldb_Library.properties
         * java_math.properties
         */
        public void setMethod(MethodInfo m)
        {

            string remarkKey;

            method = m;
            clazz = method.DeclaringType;
            fqn = null;
            specificName = null;
            sig = null;
            colsResolved = false;
            remarkKey = clazz.Name.Replace('.', '_');
            hnd_remarks = BundleHandler.getBundleHandle(remarkKey, null);
        }

        public int typeForClass(Type c)
        {

            Type to;
            int type = -1;

            if (typeMap.TryGetValue(c, out type))
            {
                return type;
            }

            // ARRAY (dimension 1)
            // HSQLDB does not yet support ARRAY for SQL, but
            // Trigger.fire takes Object[] row, which we report.
            // Also, it's just friendly to show what "would"
            // be required if/when we support ARRAY _in a broader
            // sense
            if (c.IsArray && !c.GetElementType().IsArray)
            {
                return Types.ARRAY;
            }

            try
            {
                to = Type.GetType("System.Array");

                if (to.IsAssignableFrom(c))
                {
                    return Types.ARRAY;
                }
            }
            catch (Exception ) { }

            // NUMERIC
            // All java.lang.Number impls and Decimal have
            // already been covered by lookup _in typeMap.
            // They are all final, so this is OK.
            if (typeof(double).IsAssignableFrom(c) || typeof(decimal).IsAssignableFrom(c))
            {
                return Types.NUMERIC;
            }

            // TIMESTAMP
            try
            {
                to = Type.GetType("System.DateTime");

                if (to.IsAssignableFrom(c))
                {
                    return Types.TIMESTAMP;
                }
            }
            catch (Exception ) { }

            // TIME
            try
            {
                to = Type.GetType("System.DateTime");

                if (to.IsAssignableFrom(c))
                {
                    return Types.TIMESTAMP;
                }
            }
            catch (Exception) { }

            // DATE
            try
            {
                to = Type.GetType("System.DateTime");

                if (to.IsAssignableFrom(c))
                {
                    return Types.DATE;
                }
            }
            catch (Exception ) { }

            // BLOB
            try
            {
                to = Type.GetType("java.sql.Blob");

                if (to.IsAssignableFrom(c))
                {
                    return Types.BLOB;
                }
            }
            catch (Exception ) { }

            // CLOB
            try
            {
                to = Type.GetType("java.sql.Clob");

                if (to.IsAssignableFrom(c))
                {
                    return Types.CLOB;
                }
            }
            catch (Exception ) { }

            // REF
            try
            {
                to = Type.GetType("System.Object");

                if (to.IsAssignableFrom(c))
                {
                    return Types.REF;
                }
            }
            catch (Exception ) { }

            // STRUCT
            try
            {
                to = Type.GetType("java.sql.Struct");

                if (to.IsAssignableFrom(c))
                {
                    return Types.STRUCT;
                }
            }
            catch (Exception) { }

            // LONGVARBINARY : org.hsqldb.Binary is not final
            if (typeof(EffiProz.Core.DataTypes.Binary).IsAssignableFrom(c))
            {
                return Types.LONGVARBINARY;
            }

            // LONGVARCHAR : really OTHER at this point
            try
            {

                // @since JDK1.4
                to = Type.GetType("java.lang.CharSequence");

                if (to.IsAssignableFrom(c))
                {
                    return Types.LONGVARCHAR;
                }
            }
            catch (Exception ) { }

            // we have no standard mapping for the specified class
            // at this point...is it even storable?
            if (c.IsSerializable)
            {

                // Yes: it is storable, as an OTHER.
                return Types.OTHER;
            }

            // It may (in future, say using bean contract) or may not be storable
            // (by HSQLDB)...
            // but then it may be possible to pass to an in-process routine,
            // so be lenient and just return the most generic type.
            return Types.JAVA_OBJECT;
        }
    }
}